#ifndef UUID_FDA91F49FD8F423968AA34AD0C51384B
#define UUID_FDA91F49FD8F423968AA34AD0C51384B

/* NOTE: Keep this usable from C */

#include "../support/platform.hpp"
#include "fdlibm/fdlibm.h"
#include <math.h>

#if !defined(GVL_FORCE_SSE_FPU) && GVL_FORCE_SSE2_FPU
#define GVL_FORCE_SSE_FPU 1
#endif

#if GVL_MSVCPP
/* We may want to use intrinsics */
#include <emmintrin.h>
//#pragma intrinsic(_mm_mul_sd, _mm_div_sd, _mm_mul_ss, _mm_div_ss)
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if GVL_MSVCPP
#pragma fp_contract(off)
#pragma fenv_access(on)
#pragma float_control(except, off)
#pragma float_control(precise, on)
/* NOTE: For VC++, use these flags:
** For CPUs supporting SSE2: /arch:SSE2 /D "GVL_X87=0" /D "GVL_FORCE_SSE2=1"
** For compatibility: /D "GVL_X87=1"
**
** The "GVL_FORCE_SSE2=1" define may be omitted if you're absolutely sure
** that no multiplications or divisions are done in x87. It may worsen some
** optimizations if enabled.
** With the above pragmas and operations wrapped in functions, VC++ has a
** strong aversion towards generating x87 instructions for any floating point
** operation, but one can never be sure. The easiest way to verify is
** searching assembler output for "fi?mulp?" and "fi?divp?" (regex) and make
** sure nothing is found.
*/
#elif GVL_GCC
/* NOTE: For gcc on x86, use these flags:
** For CPUs supporting SSE2: -frounding-math -msse2 -mfpmath=sse -DGVL_X87=0
** For compatibility: -frounding-math -DGVL_X87=1
**
** Flags that are recommended:
**     -fno-math-errno
**
**         Some platforms do not set errno anyway, so it's best if it's not set anywhere.
**         This can also increase performance.
*/
#endif

/* Setup FPU for IEEE compliant operation. Allows
** the g* functions to produce reproducible results. NOTE: If this
** function is used, optimizations that assume that certain FPU flags are
** set need to be disabled.
*/
void gvl_init_ieee();

/* NOTE: Not tested! */
void gvl_flush_to_zero_context(void(*func)());

#if GVL_X87

extern unsigned char const scaleup[10];
extern unsigned char const scaledown[10];

GVL_INLINE double gM(double x, double y)
{
#if GVL_MSVCPP
	/* To avoid incorrectly rounded subnormals due to
	** double rounding, we must force double precision
	** subnormals to also be extended precision
	** subnormals after the multiply. We do that by
	** multiplying one of the operands with 1 / 2^(16383 - 1023)
	** before the multiply and multiply the result
	** with 2^(16383 - 1023) afterwards.
	*/
	double r;

	__asm
	{
#if 0
		fld TBYTE PTR scaleup
		fld TBYTE PTR scaledown
		fmul x /* ST(0) = x*scaledown, ST(1) = scaleup */
		fmul y /* ST(0) = x*scaledown*y, ... */
		fmul ST(0),ST(1) /* ST(0) = x*scaledown*y*scaleup */
		fstp r
		fstp ST
#endif

		  /* This was generated by gcc, probably better: */
		fld y /* y = ST(0) */
		fld x /* x = ST(0), y = ST(1) */
		fld TBYTE PTR scaledown /* scaledown = ST(0), x = ST(1), y = ST(2) */
		fmulp ST(1), ST(0)      /* x*scaledown = ST(0), y = ST(1) */
		fmulp ST(1), ST(0)      /* (x*scaledown)*y = ST(0) */
		fld TBYTE PTR scaleup   /* scaleup = ST(0), (x*scaledown)*y = ST(1) */
		fmulp ST(1), ST(0)      /* ((x*scaledown)*y)*scaleup = ST(0) */
		fstp r                  /* r = R(((x*scaledown)*y)*scaleup) */

/* One could think this was better, TODO: investigate */
#if 0
		fld TBYTE PTR scaledown /* scaledown = ST(0) */
		fmul x                  /* x*scaledown = ST(0) */
		fmul y                  /* (x*scaledown)*y = ST(0) */
		fld TBYTE PTR scaleup   /* scaleup = ST(0), (x*scaledown)*y = ST(1) */
		fmulp ST(1), ST(0)      /* ((x*scaledown)*y)*scaleup = ST(0) */
		fstp r                  /* r = R(((x*scaledown)*y)*scaleup) */
#endif
	}

	return r;
#elif GVL_GCC
	/* GCC supports 80-bit floats, so we can use them instead of
	** inline asm to hopefully get a bit better code generation. */
	long double xe, ye;
#if !GVL_GCC_FLOAT_STORE && !GVL_TRUST_GCC_ROUND_FROM_LONG_DOUBLE /* We can skip volatile if we can trust that GCC rounds the long double to double assignment */
	volatile /* volatile to force store and load */
#endif
	double res;
	xe = x;
	ye = y;

	res = ((xe * *((long double*)scaledown)) * ye) * (*(long double*)scaleup);
	return res;
#else
#error "Not implemented"
#endif
}

GVL_INLINE double gD(double x, double y)
{
#if GVL_MSVCPP
	/* Similarily to the case with gM, we do this
	** to avoid incorrectly rounded subnormals. */
	double r;

	__asm
	{
#if 0
		fld TBYTE PTR scaleup
		fld TBYTE PTR scaledown
		fmul x /* ST(0) = x*scaledown, ST(1) = scaleup */
		fdiv y /* ST(0) = x*scaledown/y, ... */
		fmul ST(0),ST(1) /* ST(0) = x*scaledown/y*scaleup */
		fstp r
		fstp ST
#endif

		/* This was generated by gcc, probably better: */
		fld TBYTE PTR scaledown /* scaledown = ST(0) */
		fmul x                  /* x*scaledown = ST(0) */
		fdiv y                  /* (x*scaledown)/y = ST(0) */
		fld TBYTE PTR scaleup   /* scaleup = ST(0), (x*scaledown)/y = ST(1) */
		fmulp ST(1), ST(0)      /* ((x*scaledown)/y)*scaleup = ST(0) */
		fstp r                  /* r = R(((x*scaledown)/y)*scaleup) */
	}

	return r;
#elif GVL_GCC
	/* GCC supports 80-bit floats, so we can use them instead of
	** inline asm to hopefully get a bit better code generation. */
	long double xe, ye;
#if !GVL_GCC_FLOAT_STORE && !GVL_TRUST_GCC_ROUND_FROM_LONG_DOUBLE /* We can skip volatile if we can trust that GCC rounds the long double to double assignment */
	volatile /* volatile to force store and load */
#endif
	double res; /* volatile to force store and load */
	xe = x;
	ye = y;

	res = ((xe * *((long double*)scaledown)) / ye) * (*(long double*)scaleup);
	return res;
#else
#error "Not implemented"
#endif
}

/* Addition and subtraction do not suffer from incorrectly rounded
** subnormals. */

GVL_INLINE double gA(double x, double y)
{
#if GVL_GCC
#if !GVL_GCC_FLOAT_STORE
	volatile /* volatile to force store and load */
#endif
	double res;
	res = x + y;
	return res;
#else
	return x + y;
#endif
}

GVL_INLINE double gS(double x, double y)
{
#if GVL_GCC
#if !GVL_GCC_FLOAT_STORE
	volatile /* volatile to force store and load */
#endif
	double res;
	res = x - y;
	return res;
#else
	return x - y;
#endif
}

GVL_INLINE double gSqrt(double x)
{
#if GVL_GCC
#if !GVL_GCC_FLOAT_STORE
	volatile /* volatile to force store and load */
#endif
	double res;
	res = sqrt(x);
	return res;
#else
	return sqrt(x);
#endif
}

#else /* if !GVL_X87 */

GVL_INLINE double gM(double x, double y)
{
#if GVL_MSVCPP && GVL_FORCE_SSE2_FPU && !GVL_X86_64
	/* Use SSE2 intrinsics to make sure VC++ doesn't cause
	** incorrect subnormal rounding here. NOTE: This might worsen optimization
	** somewhat. */
	double r;
	_mm_store_sd(&r, _mm_mul_sd(_mm_load_sd(&x), _mm_load_sd(&y)));
	return r;
#else
	return x * y;
#endif
}

GVL_INLINE double gD(double x, double y)
{
#if GVL_MSVCPP && GVL_FORCE_SSE2_FPU && !GVL_X86_64
	/* Use SSE2 intrinsics to make sure VC++ doesn't cause
	** incorrect subnormal rounding here. NOTE: This might worsen optimization
	** somewhat. */
	double r;
	_mm_store_sd(&r, _mm_div_sd(_mm_load_sd(&x), _mm_load_sd(&y)));
	return r;
#else
	return x / y;
#endif
}

GVL_INLINE double gA(double x, double y)
{
	/* We don't force SSE2 for VC++ here even with GVL_FORCE_SSE2_FPU, because
	** addition doesn't suffer from incorrect subnormal rounding. */
	double res;
	res = x + y;
	return res;
}

GVL_INLINE double gS(double x, double y)
{
	/* We don't force SSE2 for VC++ here, because addition doesn't suffer from
	** incorrect subnormal rounding. */
	double res;
	res = x - y;
	return res;
}

GVL_INLINE double gSqrt(double x)
{
	double res;
	res = sqrt(x);
	return res;
}

// NOTE: g*f variants are currently only available with SSE or SSE2 enabled and
// not with x87.


GVL_INLINE float gMf(float x, float y)
{
#if GVL_MSVCPP && GVL_FORCE_SSE2_FPU && !GVL_X86_64
	/* Use SSE intrinsics to make sure VC++ doesn't cause
	** incorrect subnormal rounding here. NOTE: This might worsen optimization
	** somewhat. */
	float r;
	_mm_store_ss(&r, _mm_mul_ss(_mm_load_ss(&x), _mm_load_ss(&y)));
	return r;
#else
	return x * y;
#endif
}

GVL_INLINE float gDf(float x, float y)
{
#if GVL_MSVCPP && GVL_FORCE_SSE_FPU && !GVL_X86_64
	/* Use SSE intrinsics to make sure VC++ doesn't cause
	** incorrect subnormal rounding here. NOTE: This might worsen optimization
	** somewhat. */
	float r;
	_mm_store_ss(&r, _mm_div_ss(_mm_load_ss(&x), _mm_load_ss(&y)));
	return r;
#else
	return x / y;
#endif
}

GVL_INLINE float gAf(float x, float y)
{
#if GVL_MSVCPP && GVL_FORCE_SSE_FPU && !GVL_X86_64
	// VC++ generates stupid code for just (x+y) in x86. Intrinsics are actually faster!
	float r;
	_mm_store_ss(&r, _mm_add_ss(_mm_load_ss(&x), _mm_load_ss(&y)));
	return r;
#else
	return x + y;
#endif
}

GVL_INLINE float gSf(float x, float y)
{
#if GVL_MSVCPP && GVL_FORCE_SSE_FPU && !GVL_X86_64
	// VC++ generates stupid code for just (x-y) in x86. Intrinsics are actually faster!
	float r;
	_mm_store_ss(&r, _mm_sub_ss(_mm_load_ss(&x), _mm_load_ss(&y)));
	return r;
#else
	return x - y;
#endif
}

GVL_INLINE float gSqrtf(float x)
{
#if GVL_MSVCPP
	float r;
	_mm_store_ss(&r, _mm_sqrt_ss(_mm_load_ss(&x)));
	return r;
#else
	return sqrtf(x);
#endif
}
#endif

GVL_INLINE float gdtof(double x)
{
	return (float)x;
}

#ifdef __cplusplus

}

namespace gvl
{

// C++ specific features
struct rdouble
{
	rdouble()
	: value(0.0)
	{
	}

	rdouble(double value)
	: value(value)
	{

	}

	rdouble& operator+=(rdouble b)
	{
		value = gA(value, b.value);
		return *this;
	}

	rdouble& operator-=(rdouble b)
	{
		value = gS(value, b.value);
		return *this;
	}

	rdouble& operator*=(rdouble b)
	{
		value = gM(value, b.value);
		return *this;
	}

	rdouble& operator/=(rdouble b)
	{
		value = gD(value, b.value);
		return *this;
	}

	rdouble operator-() const
	{ return -value; }

	double value;
};

inline rdouble operator+(rdouble a, rdouble b)
{ return gA(a.value, b.value); }

inline rdouble operator-(rdouble a, rdouble b)
{ return gS(a.value, b.value); }

inline rdouble operator*(rdouble a, rdouble b)
{ return gM(a.value, b.value); }

inline rdouble operator/(rdouble a, rdouble b)
{ return gD(a.value, b.value); }

inline bool operator<(rdouble a, rdouble b)
{ return a.value < b.value; }

inline bool operator<=(rdouble a, rdouble b)
{ return a.value <= b.value; }

inline bool operator>(rdouble a, rdouble b)
{ return a.value > b.value; }

inline bool operator>=(rdouble a, rdouble b)
{ return a.value >= b.value; }

inline bool operator!=(rdouble a, rdouble b)
{ return a.value != b.value; }

inline bool operator==(rdouble a, rdouble b)
{ return a.value == b.value; }


inline rdouble sqrt(rdouble x)
{ return gSqrt(x.value); }

inline rdouble log(rdouble x)
{ return fd_log(x.value); }

inline rdouble cos(rdouble x)
{ return fd_cos(x.value); }

inline rdouble sin(rdouble x)
{ return fd_sin(x.value); }

inline rdouble atan2(rdouble a, rdouble b)
{ return fd_atan2(a.value, b.value); }

inline rdouble floor(rdouble x)
{ return fd_floor(x.value); }

#if !GVL_X87

struct rfloat
{
	rfloat()
	//: value(0.f)
	{
	}

	rfloat(float value)
	: value(value)
	{

	}

	explicit rfloat(double value_d)
	: value(gdtof(value_d))
	{
	}


	float value;
};


GVL_FORCE_INLINE rfloat& operator+=(rfloat& a, rfloat const& b)
{
	a.value = gAf(a.value, b.value);
	return a;
}

GVL_FORCE_INLINE rfloat& operator-=(rfloat& a, rfloat const& b)
{
	a.value = gSf(a.value, b.value);
	return a;
}

GVL_FORCE_INLINE rfloat& operator*=(rfloat& a, rfloat const& b)
{
	a.value = gMf(a.value, b.value);
	return a;
}

GVL_FORCE_INLINE rfloat& operator/=(rfloat& a, rfloat const& b)
{
	a.value = gDf(a.value, b.value);
	return a;
}

GVL_FORCE_INLINE rfloat operator-(rfloat const& a)
{ return -a.value; }

GVL_FORCE_INLINE rfloat operator+(rfloat const& a, rfloat const& b)
{ return gAf(a.value, b.value); }

GVL_FORCE_INLINE rfloat operator-(rfloat const& a, rfloat const& b)
{ return gSf(a.value, b.value); }

GVL_FORCE_INLINE rfloat operator*(rfloat const& a, rfloat const& b)
{ return gMf(a.value, b.value); }

GVL_FORCE_INLINE rfloat operator/(rfloat const& a, rfloat const& b)
{ return gDf(a.value, b.value); }

GVL_FORCE_INLINE bool operator<(rfloat const& a, rfloat const& b)
{ return a.value < b.value; }

GVL_FORCE_INLINE bool operator<=(rfloat const& a, rfloat const& b)
{ return a.value <= b.value; }

GVL_FORCE_INLINE bool operator>(rfloat const& a, rfloat const& b)
{ return a.value > b.value; }

GVL_FORCE_INLINE bool operator>=(rfloat const& a, rfloat const& b)
{ return a.value >= b.value; }

GVL_FORCE_INLINE bool operator!=(rfloat const& a, rfloat const& b)
{ return a.value != b.value; }

GVL_FORCE_INLINE bool operator==(rfloat const& a, rfloat const& b)
{ return a.value == b.value; }


GVL_FORCE_INLINE rfloat sqrt(rfloat x)
{ return gSqrtf(x.value); }

GVL_FORCE_INLINE rfloat log(rfloat x)
{ return gdtof(fd_log(x.value)); }

GVL_FORCE_INLINE rfloat cos(rfloat x)
{ return gdtof(fd_cos(x.value)); }

GVL_FORCE_INLINE rfloat sin(rfloat x)
{ return gdtof(fd_sin(x.value)); }

GVL_FORCE_INLINE rfloat atan2(rfloat a, rfloat b)
{ return gdtof(fd_atan2(a.value, b.value)); }

GVL_FORCE_INLINE rfloat floor(rfloat x)
{ return gdtof(fd_floor(x.value)); }

#endif

}

#if !GVL_X87
inline float to_float(gvl::rfloat v)
{ return v.value; }

inline float to_float(float v)
{ return v; }
#endif

#endif

#endif /* UUID_FDA91F49FD8F423968AA34AD0C51384B */
